// @file TODO
//
// @copyright Copyright (c) TODO

#ifndef _STL_ALLOCATOR_H
#define _STL_ALLOCATOR_H

// See
// http://www.codeproject.com/Articles/1089905/A-Custom-STL-std-allocator-Replacement-Improves-Performance-

#include "xallocator.h"

template <typename T>
class stl_allocator;
template <>
class stl_allocator<void> {
 public:
  typedef void* pointer;
  typedef const void* const_pointer;
  // reference to void members are impossible.
  typedef void value_type;

  template <class U>
  struct rebind {
    typedef stl_allocator<U> other;
  };
};

/// @brief stl_allocator is STL-compatible allocator used to provide fixed
/// block allocations.
/// @details The default allocator for the STL is the global heap. The
/// stl_allocator is custom allocator where xmalloc/xfree is used to obtain
/// and release memory.
template <typename T>
class stl_allocator {
 public:
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  typedef T* pointer;
  typedef const T* const_pointer;
  typedef T& reference;
  typedef const T& const_reference;
  typedef T value_type;

  /// Constructor
  stl_allocator() {}

  /// Destructor
  ~stl_allocator() {}

  /// Copy constructor
  template <class U>
  stl_allocator(const stl_allocator<U>&) {}

  template <class U>
  struct rebind {
    typedef stl_allocator<U> other;
  };

  /// Return reference address.
  /// @return  Pointer to T memory.
  pointer address(reference x) const { return &x; }

  /// Return reference address.
  /// @return  Const pointer to T memory.
  const_pointer address(const_reference x) const { return &x; }

  /// Get the maximum size of memory.
  /// @return  Max memory size in bytes.
  size_type max_size() const throw() { return size_t(-1) / sizeof(value_type); }

  /// Allocates a fixed block of memory
  /// @param[in] n - size of memory to allocate in bytes
  /// @param[in] hint
  /// @return  Pointer to the allocated memory.
  pointer allocate(size_type n,
                   stl_allocator<void>::const_pointer hint = nullptr) {
    return static_cast<pointer>(xmalloc(n * sizeof(T)));
  }

  /// Deallocate a previously allocated fixed memory block.
  /// @param[in] p - pointer to the memory block
  /// @param[in] n - size of memory in bytes
  void deallocate(pointer p, size_type n) { xfree(p); }

  /// Constructs a new instance.
  /// @param[in] p - pointer to the memory where the instance is constructed
  ///    using placement new.
  /// @param[in] val - instance of object to copy construct.
  void construct(pointer p, const T& val) {
    new (static_cast<void*>(p)) T(val);
  }

  /// Create a new object instance using placement new.
  /// @param[in] p - pointer to the memory where the instance is constructed
  ///    using placement new.
  void construct(pointer p) { new (static_cast<void*>(p)) T(); }

  /// Destroys an instance. Objects created with placement new must
  ///  explicitly call the destructor.
  /// @param[in] p - pointer to object instance.
  void destroy(pointer p) { p->~T(); }
};

template <typename T, typename U>
inline bool operator==(const stl_allocator<T>&, const stl_allocator<U>) {
  return true;
}

template <typename T, typename U>
inline bool operator!=(const stl_allocator<T>&, const stl_allocator<U>) {
  return false;
}

// For VC6/STLPort 4-5-3 see /stl/_alloc.h, line 464
// "If custom allocators are being used without member template classes support
// : user (on purpose) is forced to define rebind/get operations !!!"
#ifdef _WIN32
#define STD_ALLOC_CDECL __cdecl
#else
#define STD_ALLOC_CDECL
#endif

namespace std {
template <class _Tp1, class _Tp2>
inline stl_allocator<_Tp2>& STD_ALLOC_CDECL
__stl_alloc_rebind(stl_allocator<_Tp1>& __a, const _Tp2*) {
  return (stl_allocator<_Tp2>&)(__a);
}

template <class _Tp1, class _Tp2>
inline stl_allocator<_Tp2> STD_ALLOC_CDECL
__stl_alloc_create(const stl_allocator<_Tp1>&, const _Tp2*) {
  return stl_allocator<_Tp2>();
}
}  // namespace std

#endif
